<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<link href="styles.css" rel="stylesheet">
	<title>Octave bands frequency distribution | audioMotion-analyzer</title>
</head>

<body>
<div class="container">
	<div class="settings">
		<h1>audioMotion-analyzer</h1>
		<h2>octave bands frequency distribution</h2>
		<div class="flex">
			<label><a href="https://audiomotion.dev/#/?id=fftsize-number" class="info" data-text="[audioMotion.fftSize] Higher values provide more detail in the frequency domain, but less detail in the temporal domain (slower response)." target="_blank">FFT size</a> (samples)</label>
			<select id="fftsize">
				<option>512</option>
				<option>1024</option>
				<option>2048</option>
				<option>4096</option>
				<option selected>8192</option>
				<option>16384</option>
				<option>32768</option>
			</select>
		</div>

		<div class="flex">
			<label><a href="https://audiomotion.dev/#/?id=audioctx-audiocontext-object-read-only" class="info" data-text="[audioCtx.sampleRate] The sample rate depends on the output device, but it's usually 44100 or 48000 Hz." target="_blank">Sample rate</a> (Hz)</label>
			<select id="samplerate">
				<option>44100</option>
				<option>48000</option>
				<option>96000</option>
			</select>
		</div>

		<div class="flex">
			<label><a href="https://audiomotion.dev/#/?id=maxfreq-number" class="info" data-text="[audioMotion.minFreq] The lowest valid value is 1." target="_blank">Min. Frequency</a> (Hz)</label>
			<input id="minFreq" type="text" size="5" value="20">
		</div>

		<div class="flex">
			<label><a href="https://audiomotion.dev/#/?id=maxfreq-number" class="info" data-text="[audioMotion.maxFreq] The highest valid value is half the Sample Rate." target="_blank">Max. Frequency</a> (Hz)</label>
			<input id="maxFreq" type="text" size="5" value="22000">
		</div>

		<div class="flex">
			<label><a href="https://audiomotion.dev/#/?id=mode-number" class="info" data-text="[audioMotion.mode]" target="_blank">Visualization Mode</a></label>
			<select id="mode">
				<option value="8">Full octave bands</option>
				<option value="7">Half octave bands</option>
				<option value="6">1/3rd octave bands</option>
				<option value="5">1/4th octave bands</option>
				<option value="4">1/6th octave bands</option>
				<option value="3">1/8th octave bands</option>
				<option value="2">1/12th octave bands</option>
				<option value="1">1/24th octave bands</option>
			</select>
		</div>

		<div class="flex">
			<label for="ansi"><a href="https://audiomotion.dev/#/?id=ansibands-boolean" class="info" data-text="[audioMotion.ansiBands]" target="_blank">ANSI Standard Octave Bands</a></span></label>
			<input id="ansi" type="checkbox">
		</div>

		<label>
			<button id="generate">Update data table &gt; </button>
		</label>

		<div>
			<h4>References:</h4>
			<ul>
				<li><a href="https://en.wikipedia.org/wiki/Quarter_tone">Quarter tone</a></li>
				<li><a href="https://pages.mtu.edu/~suits/notefreqs.html">Frequencies for equal-tempered scale, A4 = 440 Hz</a></li>
				<li><a href="http://hyperphysics.phy-astr.gsu.edu/hbase/Music/et.html">Equal temperament</a></li>
				<li><a href="https://www.dacapoalcoda.com/quarter-tones">Quarter tones</a></li>
				<li><a href="https://blog.prosig.com/2006/02/17/standard-octave-bands/">Standard Octave Bands</a></li>
				<li><a href="https://www.engineeringtoolbox.com/octave-bands-frequency-limits-d_1602.html">Octave Band Frequencies</a></li>
				<li><a href="https://archive.org/details/gov.law.ansi.s1.11.2004">ANSI S1.11-2004: Specification for Octave-Band and<br>Fractional-Octave-Band Analog and Digital Filters</a></li>
			</ul>
		</div>

		<div class="credits">
			<strong>audioMotion-analyzer</strong> Copyright &copy; 2018-2025 Henrique Avila Vianna.<br>
			Licensed under AGPL-3.0 or later. Source code is available on <a href="https://github.com/hvianna/audioMotion-analyzer">GitHub</a>.
		</div>
	</div>

	<table id="tableBands">
		<thead>
			<tr>
				<th rowspan="2"><span class="info" data-text="Index in the array returned by getBars()">Bar #</span></th>
				<th colspan="3">Frequency range (Hz)</th>
				<th colspan="2"><span class="info" data-text="FFT bin selected for the lower edge">Lower FFT bin</span></th>
				<th colspan="2"><span class="info" data-text="FFT bin selected for the upper edge">Upper FFT bin</span></th>
			</tr>
			<tr>
				<th>Lower</th>
				<th>Center</th>
				<th>Upper</th>
				<th><span class="info" data-text="FFT bin index">#</span></th>
				<th><span class="info" data-text="Actual FFT frequencies and interpolation ratio applied">Frequency (Hz)</span></th>
				<th><span class="info" data-text="FFT bin index">#</span></th>
				<th><span class="info" data-text="Actual FFT frequencies and interpolation ratio applied">Frequency (Hz)</span></th>
			</th>
		</thead>
		<tbody>
		</tbody>
	</table>
</div>

<script src="generateBands.js"></script>

<script>
	function update() {

		let fftSize    = +document.getElementById('fftsize').value,
			sampleRate = +document.getElementById('samplerate').value,
			mode       = +document.getElementById('mode').value,
			minFreq    = +document.getElementById('minFreq').value,
			maxFreq    = +document.getElementById('maxFreq').value,
			ansiBands  = document.getElementById('ansi').checked,
			bodyBands  = document.getElementById('tableBands').tBodies[0];

		// make sure maxFreq is not higher than the Nyquist frequency (enforced on module by maxFreq setter)
		maxFreq = Math.min( maxFreq, sampleRate / 2 );
		document.getElementById('maxFreq').value = maxFreq;

		// generate analyzer bands
		const analyzerBars = generateBands( { fftSize, sampleRate, mode, minFreq, maxFreq, ansiBands } );

		// helper functions
		const round = freq => freq.toFixed( Math.max( 0, 3 - ( Math.log10( freq ) | 0 ) ) );
		const binToFreq = bin => round( bin * sampleRate / fftSize || 1 );

		bodyBands.innerHTML = '';

		analyzerBars.forEach( ( bar, index ) => {
			let html = `
				<tr>
					<td class="center">${index}</td>
					<td class="center${ bar.clamped == 'l' ? ' clamped" title="Lower edge will be clamped to ' + minFreq + 'Hz (minFreq)' : '' }">${ bar.freqLo }</td>
					<td class="center">${ bar.freq }</td>
					<td class="center${ bar.clamped == 'u' ? ' clamped" title="Upper edge will be clamped to ' + maxFreq + 'Hz (maxFreq)' : '' }">${ bar.freqHi }</td>
					<td>${ bar.binLo }</td>
					<td title="Interpolation ratio: ${ bar.ratioLo.toFixed(3) }"><span class="bar" style="width: ${ bar.ratioLo * 100 }%"></span>${ binToFreq( bar.binLo ) } - ${ binToFreq( bar.binLo + 1 ) }</td>
					<td>${ bar.binHi }</td>
					<td title="Interpolation ratio: ${ bar.ratioHi.toFixed(3) }"><span class="bar" style="width: ${ bar.ratioHi * 100 }%"></span>${ binToFreq( bar.binHi ) } - ${ binToFreq( bar.binHi + 1 ) }</td>
				</tr>
			`;
			bodyBands.innerHTML += html;
		});
	}

	const audioCtx = new ( window.AudioContext || window.webkitAudioContext )();
	document.getElementById('samplerate').value = audioCtx.sampleRate;

	update();
	document.getElementById('generate').addEventListener( 'click', () => update() );
</script>

</body>
</html>
